package com.bsi.core.lock;

import com.bsi.core.utils.SpringContextUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


/**
 * @ClassName RedisLock
 * @Author LiuSong
 * @Date 2020/7/2 15:13
 * @Version 1.0
 * @Description redis作为锁机制解决死锁等问题
 */
public class RedisLock {

    private RedisTemplate<String, String> redisTemplate;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);

    public RedisLock() {
    }

    public RedisLock(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * @param key   锁key
     * @param value 当前时间+超时时间（时间戳）
     * @return true 成功加锁， false 被占用
     */
    public boolean lock(String key, String value) {  //对应setnx命令
        //加锁
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            // 锁未占用，可以设置成功，也就是key不存在
            return true;
        }
        // 判断锁超时 - 防止原来的异常操作，没有执行解锁操作，防止死锁
        String currentValue = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(currentValue) &&
                Long.parseLong(currentValue) < System.currentTimeMillis()) {  // currentValue不为空并且小于当前时间
            // 获取上一个锁的时间value
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value); //对应getAndset 如果key存在
            //假设两个线程同时进来这里，因为key被占用了，而且锁过期了，获取的currentValue=A(get取的旧的值一定是一样的)
            //而这里的getAndSet一次只会一个执行，一个线程执行之后，上一个的value已经变成了B，只有一个线程获取的上一个值会是A
            if (StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)) {
                // oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的时间戳，也是防止并发
                return true;
            }
        }
        return false;
    }

    /**
     * 解锁操作
     *
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(currentValue) && value.equals(currentValue)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            LOGGER.error(">>>>>>>>>解锁异常<<<<<<<<<");
        }

    }

}
